﻿
namespace Patterns4Net.Tests.Core.CecilExtensions
{
    using System;
    using System.Linq;
    using Mono.Cecil;
    using NUnit.Framework;
    using Patterns4Net.Core.CecilExtensions;

    [TestFixture]
    public class MethodExtensionsTest
    {
        [Test]
        public void IsVoid_Returns_True_For_Foo_AbstractM()
        {
            var method = GetMethod<Foo>("AbstractM");
            Assert.IsTrue(method.IsVoid());
        }

        [Test]
        public void IsVoid_Returns_False_For_Foo_CompareTo()
        {
            var method = GetMethod<Foo>("CompareTo");
            Assert.IsFalse(method.IsVoid());
        }

        [Test]
        public void Returns_Returns_True_For_Foo_CompareTo_And_Int()
        {
            var method = GetMethod<Foo>("CompareTo");
            Assert.IsTrue(method.Returns(typeof(int)));
        }

        [Test]
        public void Returns_Returns_False_For_Foo_CompareTo_And_Double()
        {
            var method = GetMethod<Foo>("CompareTo");
            Assert.IsFalse(method.Returns(typeof(double)));
        }

        [Test]
        public void Overrideable_Returns_False_For_Foo_ToBeSealed()
        {
            var method = GetMethod<Foo>("ToBeSealed");
            Assert.IsFalse(method.IsOverriddeable());
        }

        [Test]
        public void Overrides_Returns_False_For_Foo_AbstractM_And_Bar_VirtualM()
        {
            var abstractM = GetMethod<Foo>("AbstractM");
            var virtualM = GetMethod<Bar>("VirtualM");
            Assert.IsFalse(abstractM.Overrides(virtualM));
        }

        [Test]
        public void Overrides_Returns_True_For_Foo_AbstractM_And_Bar_AbstractM()
        {
            var abstractMFoo = GetMethod<Foo>("AbstractM");
            var abstractMBar = GetMethod<Bar>("AbstractM");
            Assert.IsTrue(abstractMFoo.Overrides(abstractMBar));
        }

        [Test]
        public void Overrides_Returns_True_For_Foo_CompareTo_And_IComparable_CompareTo()
        {
            var fooCompareTo = GetMethod<Foo>("CompareTo");
            var ifaceCompateTor = GetMethod<IComparable>("CompareTo");
            Assert.IsTrue(fooCompareTo.Overrides(ifaceCompateTor));
        }

        [Test]
        public void Overrides_Returns_False_For_Overrides_Beasts_CompareTo_And_IComparable_CompareTo()
        {
            var compareTo = GetMethod<OverridesBeasts>("CompareTo");
            var ifaceCompareTo = GetMethod<IComparable>("CompareTo");
            Assert.IsFalse(compareTo.Overrides(ifaceCompareTo));
        }

        [Test]
        public void Overrides_Returns_True_For_Overrides_Beasts_Explicit_Implementation_Of_CompareTo_And_IComparable_CompareTo()
        {
            var compareTo = typeof(OverridesBeasts).GetTypeDefinition().Methods.First(m => m.Name.StartsWith("System"));
            var ifaceCompareTo = GetMethod<IComparable>("CompareTo");
            Assert.IsTrue(compareTo.Overrides(ifaceCompareTo));
        }

        [Test]
        public void Overrides_Returns_False_For_OverridesBeastsChild_M_And_OverridesBeasts_M()
        {
            var baseTypeMethod = GetMethod<OverridesBeasts>("M");
            var childMethod = GetMethod<OverridesBeastsChild>("M");
            Assert.IsFalse(childMethod.Overrides(baseTypeMethod));
        }

        private static MethodDefinition GetMethod<T>(string methodName)
        {
            var foo = typeof(T).GetTypeDefinition();
            return foo.Methods.First(m => m.Name == methodName);
        }

        public class OverridesBeasts : IComparable
        {
            public int CompareTo(object obj)
            {
                return 0;
            }

            int IComparable.CompareTo(object obj)
            {
                return 1;
            }

            public virtual void M()
            {
                int i = 2;
            }
        }

        public class OverridesBeastsChild : OverridesBeasts
        {
            public new virtual void M()
            {
                int i = 1;
            }
        }
    }
}
